package com.smartcar

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.*
import android.graphics.Bitmap
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.camera.core.ImageCapture.Metadata
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

/** Helper type alias used for analysis use case callbacks */
typealias LumaListener = (luma: Double) -> Unit

class OperationPanelFragment : Fragment() {
    private lateinit var container: ConstraintLayout
    private lateinit var viewFinder: PreviewView

    private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
    private var preview: Preview? = null
    private var imageCapture: ImageCapture? = null
    private var imageAnalysis: ImageAnalysis? = null
    private var camera: Camera? = null
    private var ipGetUtils : IpGetUtils? = null

    private lateinit var outputDirectory: File

    /** Blocking camera operations are performed using this executor */
    private lateinit var cameraExecutor: ExecutorService
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    private lateinit var viewBuletoothStateText: TextView
    private lateinit var viewIpStateText: TextView
    private lateinit var buttonUp: Button
    private lateinit var buttonDown: Button
    private lateinit var buttonLeft: Button
    private lateinit var buttonRight: Button
    private lateinit var buttonStop: Button
    private lateinit var buttonTakePicture: Button

    private var bluetoothConnected: Boolean = false
    private var bluetoothAddress : String? = null
    private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
        val bluetoothManager = requireContext().getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        bluetoothManager.adapter
    }
    private val BluetoothAdapter.isDisabled: Boolean
        get() = !isEnabled

    private val handler: Handler = Handler()

    private lateinit var bluetoothLeService: BluetoothLeService
    private lateinit var transferSocketService: TransferSocketService


    // Code to manage Service lifecycle.
    private val bluetoothServiceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(componentName: ComponentName, binder: IBinder) {
            var serviceBinder = binder as IBluetoothServiceBinder
            bluetoothLeService = serviceBinder.getService()
            if (!bluetoothLeService.initialize()) {
                Log.e(
                    TAG,
                    "Unable to initialize Bluetooth"
                )
            }
            // Automatically connects to the device upon successful start-up initialization.
            bluetoothLeService.connect(bluetoothAddress)
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            bluetoothLeService.close()
        }
    }

    private val transferSocketServiceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(componentName: ComponentName, binder: IBinder) {
            var serviceBinder = binder as ITransferSocketServiceBinder
            transferSocketService = serviceBinder.getService()
            transferSocketService.commandListenerInitialize()

            var pref = requireContext().getSharedPreferences("data", Context.MODE_PRIVATE)
            var ip: String? = pref.getString("clientIp", "")
            ip.let {
                ip->
                transferSocketService.setVideoReceiveIp(ip!!)
            }

            viewFinder.post { bindCameraUseCases() }
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            transferSocketService.close()
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val context = requireContext();

        ipGetUtils = IpGetUtils(context)

        val gattServiceIntent = Intent(context, BluetoothLeService::class.java)
        context.bindService(gattServiceIntent, bluetoothServiceConnection, Context.BIND_AUTO_CREATE)

        val transferSocketService = Intent(context, TransferSocketService::class.java)
        context.bindService(transferSocketService, transferSocketServiceConnection, Context.BIND_AUTO_CREATE)

        // Determine the output directory
        outputDirectory = MainActivity.getOutputDirectory(requireContext())

        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_operation_panel, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        container = view as ConstraintLayout
        viewFinder = container.findViewById(R.id.view_finder)

        // Initialize our background executor
        cameraExecutor = Executors.newSingleThreadExecutor()

        bluetoothAdapter?.takeIf { it.isDisabled }?.apply {
            val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
        }
        bluetoothAddress = arguments?.getString("address")

        viewBuletoothStateText = view.findViewById<TextView>(R.id.buletooth_state_text)
        viewIpStateText = view.findViewById<TextView>(R.id.ip_text)

        ipGetUtils.let {
            ipGetUtils ->
            if(ipGetUtils!!.isWifiOpened()){
                viewIpStateText.setText(ipGetUtils.getLocalIpAddress())
            }
        }

        buttonUp = view.findViewById<Button>(R.id.button_up)
        buttonDown = view.findViewById<Button>(R.id.button_down)
        buttonLeft = view.findViewById<Button>(R.id.button_left)
        buttonRight = view.findViewById<Button>(R.id.button_right)
        buttonStop = view.findViewById<Button>(R.id.button_stop)
        buttonTakePicture = view.findViewById<Button>(R.id.button_take_picture)

        buttonUp.setOnClickListener {
            if(bluetoothConnected){
                bluetoothLeService.writeCharacteristic('a')
            }
        }
        buttonDown.setOnClickListener {
            if(bluetoothConnected){
                bluetoothLeService.writeCharacteristic('b')
            }
        }
        buttonLeft.setOnClickListener {
            if(bluetoothConnected){
                bluetoothLeService.writeCharacteristic('c')
            }
        }
        buttonRight.setOnClickListener {
            if(bluetoothConnected){
                bluetoothLeService.writeCharacteristic('d')
            }
        }
        buttonStop.setOnClickListener {
            if(bluetoothConnected){
                bluetoothLeService.writeCharacteristic('e')
            }
        }
        buttonTakePicture.setOnClickListener {
            tackPicture()
        }
    }

    private fun dispatchCommand(raw: String){
        var part = raw.split(':')
        if(part.count()>=2){
            when(part[0]){
                "cmd" -> {
                    bluetoothLeService.writeCharacteristic(part[1][0])
                }
                "ip"->{
                    var clientIp = part[1]
                    var editor = requireContext().getSharedPreferences("data",Context.MODE_PRIVATE).edit();
                    editor.putString("clientIp",clientIp);
                    editor.commit();
                    transferSocketService.setVideoReceiveIp(clientIp)
                }
                "takePic"->{
                    tackPicture()
                }
            }
        }
    }

    /** Declare and bind preview, capture and analysis use cases */
    private fun bindCameraUseCases() {

        // Get screen metrics used to setup camera for full screen resolution
        val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
        Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")

        val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
        Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")

        val rotation = viewFinder.display.rotation

        // Bind the CameraProvider to the LifeCycleOwner
        val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
        cameraProviderFuture.addListener(Runnable {

            // CameraProvider
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview
            preview = Preview.Builder()
                // We request aspect ratio but no resolution
                .setTargetAspectRatio(screenAspectRatio)
                // Set initial target rotation
                .setTargetRotation(rotation)
                .build()

            // Attach the viewfinder's surface provider to preview use case
            preview?.setSurfaceProvider(viewFinder.previewSurfaceProvider)

            // ImageCapture
            imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                // We request aspect ratio but no resolution to match preview config, but letting
                // CameraX optimize for whatever specific resolution best fits our use cases
                .setTargetAspectRatio(screenAspectRatio)
                // Set initial target rotation, we will have to call this again if rotation changes
                // during the lifecycle of this use case
                .setTargetRotation(rotation)
                .build()

            // ImageAnalysis
            imageAnalysis = ImageAnalysis.Builder()
                // We request aspect ratio but no resolution
                .setTargetAspectRatio(screenAspectRatio)
                // Set initial target rotation, we will have to call this again if rotation changes
                // during the lifecycle of this use case
                .setTargetRotation(rotation)
                .build()
                // The analyzer can then be assigned to the instance
                .also {
                    it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
                        // Values returned from our analyzer are passed to the attached listener
                        // We log image analysis results here - you should do something useful
                        // instead!`
                        Log.d(TAG, "Average luminosity: $luma")
                    }.also {
                        analyzer->
                        analyzer.SetTransferSocketService(transferSocketService)
                        analyzer.SetNV21Image(NV21Image(requireContext()))
                    })
                }


//            imageAnalysis.setAnalyzer(object : ImageAnalysis.Analyzer() {
//                override fun analyze(ImageProxy imageProxy, int rotationDegrees) {
//                }
//            })

            // Must unbind the use-cases before rebinding them
            cameraProvider.unbindAll()

            try {
                // A variable number of use-cases can be passed here -
                // camera provides access to CameraControl & CameraInfo
                camera = cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture, imageAnalysis)
            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(requireContext()))
    }


    private fun aspectRatio(width: Int, height: Int): Int {
        val previewRatio = max(width, height).toDouble() / min(width, height)
        if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
            return AspectRatio.RATIO_4_3
        }
        return AspectRatio.RATIO_16_9
    }


    private fun updateConnectionState(resourceId: Int) {
        viewBuletoothStateText.setText(resourceId)
    }

    private fun displayData(data: String?) {
        if (data != null) {
            viewBuletoothStateText.setText(data)
        }
    }

    override fun onResume() {
        super.onResume()
        requireContext().registerReceiver(
            broadcastReceiver,
            intentFilter()
        )
    }

    override fun onPause() {
        super.onPause()
        requireContext().unregisterReceiver(broadcastReceiver)
    }

    override fun onDestroy() {
        super.onDestroy()
        requireContext().unbindService(bluetoothServiceConnection)
        requireContext().unbindService(transferSocketServiceConnection)
    }

    fun tackPicture(){

        // Get a stable reference of the modifiable image capture use case
        imageCapture?.let { imageCapture ->
            val photoFile = createFile(outputDirectory, FILENAME, PHOTO_EXTENSION)

            // Setup image capture metadata
            val metadata = Metadata().apply {

                // Mirror image when using the front camera
                isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
            }

            // Create output options object which contains file + metadata
            val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
                .setMetadata(metadata)
                .build()

            // Setup image capture listener which is triggered after photo has been taken
            imageCapture.takePicture(
                outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
                    override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                    }

                    override fun onError(exc: ImageCaptureException) {
                        Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
                    }
                })
        }
        // Create output file to hold the image
    }

    private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val action = intent.action
            if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
                bluetoothConnected = true
                updateConnectionState(R.string.connected)
            } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
                bluetoothConnected = false
                updateConnectionState(R.string.disconnected)
                findNavController().navigate(R.id.action_operationPanelFragment_to_bluetoothScanFragment)
            } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { // Show all the supported services and characteristics on the user interface.
                //displayGattServices(mBluetoothLeService.getSupportedGattServices())
            } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
                displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA))
            } else if (TransferSocketService.CAR_COMMAND.equals(action)) {
                var data = intent.getStringExtra("data")
                dispatchCommand(data)
            }
        }
    }

    private fun intentFilter(): IntentFilter? {
        val intentFilter = IntentFilter()
        intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED)
        intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED)
        intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED)
        intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE)
        intentFilter.addAction(TransferSocketService.CAR_COMMAND)
        return intentFilter
    }

    private class LuminosityAnalyzer(listener: LumaListener? = null) : ImageAnalysis.Analyzer {
        var pretime = System.currentTimeMillis()
        private var transferSocketService: TransferSocketService? = null
        private var nv21Image: NV21Image? = null

        fun SetTransferSocketService(service: TransferSocketService){
            transferSocketService = service
        }
        fun SetNV21Image(nv21: NV21Image){
            nv21Image = nv21
        }

        /**
         * Helper extension function used to extract a byte array from an image plane buffer
         */
        private fun ByteBuffer.toByteArray(): ByteArray {
            rewind()    // Rewind the buffer to zero
            val data = ByteArray(remaining())
            get(data)   // Copy the buffer into a byte array
            return data // Return the byte array
        }

        override fun analyze(image: ImageProxy) {
            val yBuffer = image.planes[0].buffer // Y
            val uBuffer = image.planes[1].buffer // U
            val vBuffer = image.planes[2].buffer // V
            val ySize = yBuffer.remaining()
            val uSize = uBuffer.remaining()
            val vSize = vBuffer.remaining()
            var curtime = System.currentTimeMillis()
            var timespan = curtime - pretime
            pretime = curtime
            val nv21 = ByteArray(ySize + uSize + vSize)
            //U and V are swapped
            yBuffer.get(nv21, 0, ySize)
            vBuffer.get(nv21, ySize, vSize)
            uBuffer.get(nv21, ySize + vSize, uSize)

            var bitmap = nv21Image!!.toBitmap(nv21, image.width, image.height)
            val output = ByteArrayOutputStream()
            bitmap.compress(Bitmap.CompressFormat.JPEG, 15, output)
            var imageBytes = output.toByteArray()

            transferSocketService?.transferVideo(imageBytes)
            output.close()
            image.close()
        }
    }


    companion object {
        private const val REQUEST_ENABLE_BT = 1
        private const val TAG: String = "OperationPanelFragment"
        private const val FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"
        private const val PHOTO_EXTENSION = ".jpg"
        private const val RATIO_4_3_VALUE = 4.0 / 3.0
        private const val RATIO_16_9_VALUE = 16.0 / 9.0
        /** Helper function used to create a timestamped file */
        private fun createFile(baseFolder: File, format: String, extension: String) =
            File(baseFolder, SimpleDateFormat(format, Locale.US)
                .format(System.currentTimeMillis()) + extension)    }


}
